Skip to content

ROS 2 Launch 使用指南:参数、条件与生命周期编排 ​

launch 文件是 ROS2 系统的启动清单:一次拉起多个节点、传参数、做条件分支、编排启动顺序、挂调试器,都写在这里。但 launch 不只是"配置文件"——它是一个用 Python 描述的、事件驱动的动作执行系统。本文基于 launch 与 launch_ros 源码(Foxy 基准,个别 CLI 选项的版本差异已标注)系统整理它的使用方法:从 Node 参数、Substitution 动态表达式,到事件处理、组件化容器与生命周期编排,适合从教程走向实际工程配置的开发者。

框架本身的两层职责划分(launch 与 launch_ros 的关系)见launch 与 launch_ros 的分层架构关系。

一、系统架构 ​

┌─────────────────────────────────────────────┐
│                   LaunchDescription          │
│  (动作蓝图:声明要执行的 Action 列表)          │
└─────────────────────┬───────────────────────┘
                       │ include_launch_description()
┌─────────────────────▼───────────────────────┐
│                    LaunchService             │
│  (事件循环驱动引擎,asyncio 驱动)              │
│  ┌──────────────────────────────────────┐   │
│  │              LaunchContext           │   │
│  │  launch_configurations(配置字典)     │   │
│  │  environment variables(环境变量)     │   │
│  │  event handlers(事件处理器注册表)    │   │
│  └──────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

LaunchService 内部使用 asyncio 事件循环,每个进程的启动、I/O、退出都通过异步事件驱动,不会阻塞主循环。

包 职责
launch 平台无关核心框架(ExecuteProcess、GroupAction、TimerAction 等)
launch_ros ROS 2 专用扩展(Node、LifecycleNode、ComposableNodeContainer 等)
ros2launch ros2 launch CLI 命令实现

二、launch 文件结构 ​

标准入口(被 ros2 launch 命令识别):

python
from launch import LaunchDescription

def generate_launch_description() -> LaunchDescription:
    return LaunchDescription([
        # 所有 actions 放在这里
    ])

可编程入口(适合复杂逻辑,直接用 Python 运行):

python
import sys
from launch import LaunchService, LaunchDescription

def main():
    ld = LaunchDescription()
    ld.add_action(...)
    ls = LaunchService(argv=sys.argv[1:])
    ls.include_launch_description(ld)
    return ls.run()

if __name__ == '__main__':
    sys.exit(main())

generate_launch_description() 是 ros2 launch 识别的固定入口名,必须返回 LaunchDescription 对象;main() 形式需要手动管理 LaunchService,适合启动前打印调试信息的场景。

三、Node Action 详解 ​

Node 是 ExecuteProcess 的子类,会自动将 ROS 专有参数(name、namespace、parameters、remappings)转换为 --ros-args 命令行参数。

python
from launch_ros.actions import Node

Node(
    package='my_pkg',            # ROS 包名(必填,除非 executable 是绝对路径)
    executable='my_node',        # 可执行文件名(必填)
    name='my_node_renamed',      # 覆盖节点名称(可选)
    namespace='/my_ns',          # 命名空间,绝对路径以 / 开头(可选)
    exec_name='label_in_log',    # 进程在日志中的标签,默认取 executable 的 basename
    parameters=[                 # 参数列表(可混合文件路径和字典)
        '/path/to/params.yaml',
        {'use_sim_time': True, 'max_speed': 1.5},
        {'nested': {'key': 'value'}},
    ],
    remappings=[                 # 话题/服务重映射(有序,顺序影响结果)
        ('chatter', 'my_chatter'),
        ('/tf', '/robot/tf'),
    ],
    arguments=['--ros-args', '--log-level', 'debug'],  # 额外命令行参数
    output='screen',             # 输出方式: 'screen' | 'log' | 'both'
    emulate_tty=True,            # 模拟 TTY,使 Python 进程输出不被缓冲
    respawn=True,                # 进程异常退出后自动重启
    respawn_delay=2.0,           # 重启前等待秒数
    prefix='gdb -ex run --args', # 进程前缀,用于 gdb/valgrind 等调试工具
    cwd='/tmp',                  # 工作目录
    env={'MY_VAR': 'value'},     # 完全替换环境变量(None 则继承当前环境)
    additional_env={'MY_VAR': 'value'},  # 在当前环境基础上追加
    shell=False,                 # 是否通过 shell 执行
    log_cmd=True,                # 打印展开后的完整命令(调试替换表达式用)
    on_exit=[...],               # 进程退出时执行的 actions
    condition=IfCondition(...),  # 条件执行
)

参数文件(YAML)格式:

yaml
# 精确匹配节点名称(精确名称优先级高于通配符)
/my_namespace/my_node_name:
  ros__parameters:
    param_a: 42

# 通配符(匹配所有节点)
/**:
  ros__parameters:
    use_sim_time: false

当 Node 未指定 name 时,launch 系统以通配符 /** 作为参数文件的键;节点名称完全确定时,用完整节点名作为键,可精确覆盖通配符参数。

传递参数的三种方式:

python
Node(
    parameters=[
        # 方式 1:YAML 文件路径(字符串或 Substitution)
        '/abs/path/to/params.yaml',
        PathJoinSubstitution([FindPackage('my_pkg'), 'config', 'params.yaml']),
        # 方式 2:Python 字典(在运行时写入临时 YAML 文件)
        {'use_sim_time': LaunchConfiguration('use_sim_time')},
        # 方式 3:单个 Parameter 对象(通过 -p key:=value 传递)
        Parameter('log_level', 'info'),
    ]
)

四、Substitution——动态替换表达式 ​

Substitution 是惰性求值的占位符,在 Action 真正执行时才被解析为字符串,可以任意嵌套组合。

Substitution 作用 示例
LaunchConfiguration(name, default=...) 读取 launch 参数 LaunchConfiguration('use_sim_time', default='false')
EnvironmentVariable(name) 读取环境变量 EnvironmentVariable('ROS_DOMAIN_ID')
FindExecutable(name=...) 查找可执行文件路径 FindExecutable(name='python3')
PathJoinSubstitution([...]) 路径拼接 见下
PythonExpression([...]) 执行 Python 表达式(沙盒,可用 math 符号) 见下
ThisLaunchFileDir() / ThisLaunchFile() 当前 launch 文件所在目录 / 完整路径 ——
LocalSubstitution(expr) 访问运行时上下文局部变量 事件回调中读 event.reason
FindPackage(pkg) (launch_ros) 包的 share 目录 FindPackage('rviz2')

组合示例:

python
from launch.substitutions import PathJoinSubstitution, PythonExpression, LaunchConfiguration
from launch_ros.substitutions import FindPackage

# 路径拼接
PathJoinSubstitution([FindPackage('my_pkg'), 'config', 'my_params.yaml'])

# 逻辑与数学运算
PythonExpression(["'true' if '", LaunchConfiguration('use_gpu'), "' == 'true' else 'false'"])
PythonExpression(['1.0 / ', LaunchConfiguration('hz')])

# 多个 Substitution 放在列表中,执行时按顺序拼接
Node(
    name=['robot_', LaunchConfiguration('robot_id')],   # → "robot_0"
    namespace=['/fleet/', LaunchConfiguration('fleet_name')],
)

五、条件执行 ​

所有 Action 都支持 condition 参数,条件不满足时该 Action 直接跳过。

python
from launch.conditions import IfCondition, UnlessCondition, \
    LaunchConfigurationEquals, LaunchConfigurationNotEquals

# IfCondition:参数字符串求值为 'true' 或 '1'(忽略大小写)时执行;
# 其余值(含 'yes')均视为假
Node(package='rviz2', executable='rviz2',
     condition=IfCondition(LaunchConfiguration('launch_rviz')))

# UnlessCondition 相反:参数不为 'true'/'1' 时执行('false'/'0'/'no' 都会执行)
Node(package='fake_node', executable='fake',
     condition=UnlessCondition(LaunchConfiguration('use_real_robot')))

# 精确等于 / 不等于
Node(condition=LaunchConfigurationEquals('backend', 'gazebo'), ...)
Node(condition=LaunchConfigurationNotEquals('backend', 'sim'), ...)

# 用 PythonExpression 组合复杂条件
Node(condition=IfCondition(PythonExpression([
    "'", LaunchConfiguration('mode'), "' in ['gpu', 'turbo']"
])), ...)

注意 'yes' 不是 IfCondition 的有效真值——判断集合只有 'true' 和 '1'。

六、参数声明——DeclareLaunchArgument ​

必须在 LaunchDescription 顶层无条件声明,否则 ros2 launch --show-args 无法显示该参数,且可能在运行时报错。

python
from launch.actions import DeclareLaunchArgument

# 必填参数(无默认值,未提供时报错)
DeclareLaunchArgument('robot_name', description='机器人名称,用于命名空间前缀')

# 可选参数(有默认值,choices 限制合法值)
DeclareLaunchArgument('use_sim_time', default_value='false',
                      choices=['true', 'false'], description='是否使用仿真时钟')

# 默认值可以是 Substitution(运行时才求值)
DeclareLaunchArgument('config_file',
                      default_value=PathJoinSubstitution([
                          FindPackage('my_pkg'), 'config', 'default.yaml']))

命令行传参:

bash
ros2 launch my_pkg my_launch.py use_sim_time:=true robot_name:=robot1
ros2 launch my_pkg my_launch.py --show-args    # 查看所有可用参数

七、GroupAction 与命名空间 ​

GroupAction 默认 scoped=True,通过 Push/Pop 机制自动隔离 launch 配置和环境变量,内部修改不泄漏到外部作用域。

python
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace, Node

# 给一组节点统一加命名空间(GroupAction 结束时自动弹出)
GroupAction([
    PushRosNamespace('robot1'),
    Node(package='my_pkg', executable='sensor'),
    Node(package='my_pkg', executable='controller'),
])

# 嵌套命名空间:/fleet/robot1、/fleet/robot2
GroupAction([
    PushRosNamespace('fleet'),
    GroupAction([PushRosNamespace('robot1'), Node(...)]),
    GroupAction([PushRosNamespace('robot2'), Node(...)]),
])

# 不隔离作用域(内部修改会影响后续 actions)
GroupAction(actions=[...], scoped=False)

PushRosNamespace 本质是设置 launch_configurations['ros_namespace'],Node 执行时读取此值并与自身 namespace 参数合并。

八、嵌套 launch 文件 ​

python
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource, AnyLaunchDescriptionSource

# 包含同目录 launch 文件并传参
IncludeLaunchDescription(
    PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/sensors.launch.py']),
    launch_arguments={'use_sim_time': LaunchConfiguration('use_sim_time')}.items()
)

# 包含其他包的 launch 文件(AnyLaunchDescriptionSource 自动识别 .py/.xml/.yaml)
IncludeLaunchDescription(
    AnyLaunchDescriptionSource([FindPackage('nav2_bringup'), '/launch/navigation_launch.py']),
    launch_arguments=[('map', '/maps/office.yaml')]
)

注意:launch_arguments 的键必须与被包含文件中 DeclareLaunchArgument 声明的名称一致,否则该值被设置为全局 launch 配置(不报错,但可能产生意外行为);被包含文件的必填参数未传入时,执行时抛 RuntimeError。

九、事件处理系统 ​

事件系统是 launch 框架的核心,进程生命周期管理全部通过事件驱动实现。

可用事件处理器 ​

事件处理器 触发时机 关键参数
OnProcessStart 进程启动后 target_action、on_start
OnProcessExit 进程退出后 target_action、on_exit
OnProcessIO 进程产生 stdout/stderr 时 target_action、on_stdout、on_stderr
OnShutdown 整个 launch 系统关闭时 on_shutdown
OnExecutionComplete action 执行完成时 target_action、on_completion
OnStateTransition (launch_ros) 生命周期节点状态转换时 target_lifecycle_node、goal_state

省略 target_action 时监听所有该类型的进程事件。

进程退出时执行动作 ​

python
from launch.actions import RegisterEventHandler, EmitEvent, LogInfo
from launch.event_handlers import OnProcessExit, OnProcessStart
from launch.events import Shutdown

server = Node(package='my_pkg', executable='server')
client = Node(package='my_pkg', executable='client')

# 服务端启动后才启动客户端(避免竞态)
RegisterEventHandler(OnProcessStart(
    target_action=server,
    on_start=[LogInfo(msg='服务端已启动'), client]))

# 服务端退出时关闭整个 launch
RegisterEventHandler(OnProcessExit(
    target_action=server,
    on_exit=[EmitEvent(event=Shutdown(reason='server exited'))]))

捕获进程输出 ​

事件回调可以直接返回 Action,launch 系统立即执行——适合对进程输出做模式匹配并联动控制:

python
def handle_stdout(event):
    line = event.text.decode().strip()
    if 'FATAL' in line:
        return EmitEvent(event=Shutdown(reason=f'进程报错: {line}'))

RegisterEventHandler(OnProcessIO(
    target_action=my_proc, on_stdout=handle_stdout, on_stderr=handle_stdout))

进程关闭信号链 ​

ExecuteProcess(及 Node)内置三阶段信号链:收到 Shutdown 事件后先发 SIGINT,等待 sigterm_timeout(默认 5s)超时发 SIGTERM,再等 sigkill_timeout(默认 5s)超时发 SIGKILL。调整超时:

bash
ros2 launch my_pkg my_launch.py sigterm_timeout:=10 sigkill_timeout:=3

十、定时器与延迟执行 ​

python
from launch.actions import TimerAction

# 5 秒后启动节点
TimerAction(period=5.0, actions=[Node(package='my_pkg', executable='delayed_node')])

# 延迟时间用 LaunchConfiguration 指定
TimerAction(period=LaunchConfiguration('startup_delay', default='2.0'), actions=[...])

# cancel_on_shutdown=False:收到 Shutdown 后仍执行(适合关机清理逻辑)
TimerAction(period=1.0, actions=[cleanup_action], cancel_on_shutdown=False)

TimerAction 是一次性的(one-shot),触发后不会重复;基于 asyncio,精度受事件循环负载影响。

十一、组件化节点(Composable Nodes) ​

多个组件节点共享同一进程(component_container),消除进程间通信开销,可启用进程内零拷贝通信。

启动时一并加载:

python
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode

ComposableNodeContainer(
    name='image_pipeline_container',
    namespace='/',
    package='rclcpp_components',
    executable='component_container',
    composable_node_descriptions=[
        ComposableNode(package='image_proc', plugin='image_proc::RectifyNode',
                       name='rectify_node', namespace='camera',
                       remappings=[('image', 'image_raw')]),
        ComposableNode(package='image_proc', plugin='image_proc::ResizeNode',
                       name='resize_node', namespace='camera',
                       extra_arguments=[{'use_intra_process_comms': True}]),  # 启用零拷贝
    ],
    output='screen',
)

向已运行的容器动态加载:

python
from launch_ros.actions import LoadComposableNodes

LoadComposableNodes(
    target_container='/image_pipeline_container',
    composable_node_descriptions=[
        ComposableNode(package='my_pkg', plugin='my_pkg::MyPlugin', name='dynamic_node'),
    ],
)

ComposableNode 关键参数:package/plugin(必填,plugin 为完整类名如 image_proc::RectifyNode)、name/namespace/parameters/remappings(同 Node)、extra_arguments(容器专用参数如 use_intra_process_comms)。

十二、生命周期节点管理 ​

LifecycleNode 在 Node 基础上自动订阅 /<name>/transition_event 话题并创建 /<name>/change_state 服务客户端,实现事件驱动的状态机管理。完整编排示例:

python
from launch import LaunchDescription
from launch.actions import RegisterEventHandler, EmitEvent, LogInfo
from launch.events import matches_action
from launch_ros.actions import LifecycleNode
from launch_ros.events.lifecycle import ChangeState
from launch_ros.event_handlers import OnStateTransition
import lifecycle_msgs.msg

def generate_launch_description():
    talker = LifecycleNode(name='talker', namespace='',
                           package='lifecycle', executable='lifecycle_talker', output='screen')

    # inactive → activate
    on_inactive = RegisterEventHandler(OnStateTransition(
        target_lifecycle_node=talker, goal_state='inactive',
        entities=[
            LogInfo(msg="talker 到达 inactive,正在激活..."),
            EmitEvent(event=ChangeState(
                lifecycle_node_matcher=matches_action(talker),
                transition_id=lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE)),
        ]))

    # active → 启动 listener
    on_active = RegisterEventHandler(OnStateTransition(
        target_lifecycle_node=talker, goal_state='active',
        entities=[LifecycleNode(name='listener', namespace='',
                                package='lifecycle', executable='lifecycle_listener', output='screen')]))

    # 触发 configure 转换
    configure_event = EmitEvent(event=ChangeState(
        lifecycle_node_matcher=matches_action(talker),
        transition_id=lifecycle_msgs.msg.Transition.TRANSITION_CONFIGURE))

    return LaunchDescription([on_inactive, on_active, talker, configure_event])

生命周期状态机:

未配置 (Unconfigured)
    │ TRANSITION_CONFIGURE
    ▼
非活跃 (Inactive)
    │ TRANSITION_ACTIVATE ⟷ TRANSITION_DEACTIVATE
    ▼
活跃 (Active)

非活跃 --TRANSITION_CLEANUP--> 未配置
未配置 --TRANSITION_UNCONFIGURED_SHUTDOWN--> 终止 (Finalized)

注意事件处理器必须在节点 action 之前放入 LaunchDescription,否则转换事件发生时处理器还未注册。

十三、OpaqueFunction——完全自定义执行逻辑 ​

OpaqueFunction 接受一个 Python 函数,执行时传入完整的 LaunchContext,可以访问所有运行时状态并动态返回 actions:

python
from launch.actions import OpaqueFunction

def setup_nodes(context, prefix, count):
    # LaunchConfiguration 的值只能在执行期通过 perform(context) 取出
    return [Node(package='my_pkg', executable='robot_controller',
                 namespace=f'{prefix}_{i}') for i in range(count)]

OpaqueFunction(function=setup_nodes, args=('robot', 3))

适合"节点数量取决于运行期参数"这类无法静态声明的场景。

十四、进程 Output 控制 ​

值 效果
'log'(默认) stdout 到日志文件,stderr 同时到屏幕和日志
'screen' stdout 和 stderr 均输出到屏幕
'both' stdout 和 stderr 均输出到屏幕和日志

Python 进程在非 TTY 环境下默认行缓冲、输出不及时,emulate_tty=True 可解决。全局覆盖输出模式可用环境变量 OVERRIDE_LAUNCH_PROCESS_OUTPUT=screen。

十五、命令行使用 ​

bash
# 从包名/绝对路径启动
ros2 launch <package> <file.launch.py> [arg:=value ...]
ros2 launch /abs/path/to/file.launch.py [arg:=value ...]

# 查看支持的参数及其默认值、描述
ros2 launch my_pkg my_launch.py --show-args

# 打印 LaunchDescription 结构(不实际执行)
# 注意:--print-description 在 Humble 及之后的版本可用,Foxy 无此选项
ros2 launch my_pkg my_launch.py --print-description

# 调试模式
ros2 launch my_pkg my_launch.py --debug

# 为所有进程添加前缀(调试工具)
ros2 launch my_pkg my_launch.py 'launch-prefix:=gdb -ex run --args'
ros2 launch my_pkg my_launch.py 'launch-prefix:=valgrind --leak-check=full'

# 非交互模式(禁用 Ctrl+C 交互处理,适合 CI)
ros2 launch my_pkg my_launch.py --noninteractive

十六、完整实战示例 ​

一个包含参数声明、条件节点、命名空间分组、子 launch 嵌套、延迟启动、进程监控的完整示例:

python
from launch import LaunchDescription
from launch.actions import (
    DeclareLaunchArgument, GroupAction,
    IncludeLaunchDescription, RegisterEventHandler,
    TimerAction, EmitEvent, LogInfo,
)
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit
from launch.events import Shutdown
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import (
    LaunchConfiguration, PathJoinSubstitution, PythonExpression, ThisLaunchFileDir,
)
from launch_ros.actions import Node, PushRosNamespace, ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
from launch_ros.substitutions import FindPackage


def generate_launch_description():

    # 1. 参数声明
    args = [
        DeclareLaunchArgument('robot_name', default_value='my_robot',
                              description='机器人名称,用于 ROS 命名空间'),
        DeclareLaunchArgument('use_sim_time', default_value='false',
                              choices=['true', 'false'], description='是否使用仿真时钟'),
        DeclareLaunchArgument('launch_rviz', default_value='true',
                              choices=['true', 'false'], description='是否启动 RViz2'),
        DeclareLaunchArgument('config_dir',
                              default_value=PathJoinSubstitution([FindPackage('my_pkg'), 'config']),
                              description='配置文件目录'),
    ]

    use_sim_time = LaunchConfiguration('use_sim_time')
    config_dir = LaunchConfiguration('config_dir')

    # 2. 主节点组(带命名空间隔离 + 参数文件)
    controller_node = Node(
        package='my_pkg', executable='controller', name='controller',
        parameters=[
            PathJoinSubstitution([config_dir, 'ctrl.yaml']),
            {'use_sim_time': PythonExpression(["'", use_sim_time, "' == 'true'"])},
        ],
        output='screen', respawn=True, respawn_delay=2.0)

    main_group = GroupAction([
        PushRosNamespace(LaunchConfiguration('robot_name')),
        controller_node,
        Node(package='my_pkg', executable='sensor_node', name='sensor',
             remappings=[('raw_data', 'processed_data')], output='screen'),
    ])

    # 3. 图像处理组件容器(进程内零拷贝)
    image_container = ComposableNodeContainer(
        name='image_container', namespace='/',
        package='rclcpp_components', executable='component_container',
        composable_node_descriptions=[
            ComposableNode(package='image_proc', plugin='image_proc::RectifyNode',
                           name='rectify',
                           extra_arguments=[{'use_intra_process_comms': True}]),
        ],
        output='screen')

    # 4. 可选 RViz(条件执行)
    rviz_node = Node(
        package='rviz2', executable='rviz2', name='rviz2',
        condition=IfCondition(LaunchConfiguration('launch_rviz')),
        arguments=['-d', PathJoinSubstitution([config_dir, 'view.rviz'])],
        output='screen')

    # 5. 延迟 3 秒后包含传感器子 launch
    delayed_sensors = TimerAction(period=3.0, actions=[
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/sensors.launch.py']),
            launch_arguments={'use_sim_time': use_sim_time}.items())
    ])

    # 6. 控制器退出时关闭整个 launch(看门狗)
    watchdog = RegisterEventHandler(OnProcessExit(
        target_action=controller_node,
        on_exit=[LogInfo(msg='控制器已退出,关闭 launch 系统...'),
                 EmitEvent(event=Shutdown(reason='controller exited'))]))

    return LaunchDescription(args + [main_group, image_container, rviz_node,
                                     delayed_sensors, watchdog])

附录:全量 Action 速查表 ​

Action 说明
ExecuteProcess 启动任意系统进程
Node (launch_ros) 启动 ROS 节点
IncludeLaunchDescription 嵌套包含另一个 launch 文件
GroupAction 将多个 action 打组,支持作用域隔离
DeclareLaunchArgument 声明命令行可传入的参数
SetLaunchConfiguration 运行时设置 launch 配置变量
SetEnvironmentVariable / UnsetEnvironmentVariable 设置/删除环境变量
RegisterEventHandler / UnregisterEventHandler 注册/注销事件监听器
EmitEvent 手动触发一个事件
LogInfo 打印 INFO 级别日志
TimerAction 延迟指定秒数后执行一组 actions(一次性)
OpaqueFunction 执行任意 Python 函数,动态返回 actions
Shutdown 触发整个 launch 系统关闭
LifecycleNode (launch_ros) 启动生命周期节点,支持状态机事件驱动
ComposableNodeContainer (launch_ros) 启动 rclcpp 组件容器
LoadComposableNodes (launch_ros) 向已运行容器动态加载组件节点
PushRosNamespace (launch_ros) 压入 ROS 命名空间(GroupAction 结束时自动弹出)
SetParameter / SetRemap (launch_ros) 设置全局参数 / 话题重映射规则

要点 ​

  • launch 是事件驱动的动作执行系统而非静态配置:LaunchDescription 是蓝图,LaunchService 用 asyncio 驱动,进程启停、输出、退出都是事件
  • Substitution 惰性求值是贯穿全文的机制——想取运行期的值(参数、环境变量、上下文),一律通过 Substitution 而不是 Python 变量提前算好
  • IfCondition 的真值集合只有 'true' 与 '1',其他任何字符串都视为假
  • 事件处理器(RegisterEventHandler)要放在触发源 action 之前注册,尤其是生命周期编排与启动顺序依赖
  • 组件化容器 + use_intra_process_comms 是图像管线等大数据流场景的标准组合;launch-prefix 挂 gdb/valgrind 是调试组合的标准入口

相关文章 ​

最近更新

基于 VitePress 构建